package pow

import (
	"goqk/blockchain"
	"goqk/core/common"
	"goqk/core/crytography"
	"goqk/core/gossip"
	"goqk/core/tree"
	"goqk/excitation"
	"log"
	"strconv"
	"strings"
	"sync"
	"time"
)

// var tmpBlocks *map[string][]byte

// Trans : 共识内容--交易,抽象为一条记录
// var Trans *map[string][]byte

// Ledger - 账本
var Ledger *blockchain.Ledger

// Pow type.
type Pow struct {
	// client        p2p.Client
	// ServiceName   string
	difficulty    int
	nodeID        string
	beltline      excitation.Beltline
	blockGossiper *gossip.Gossiper
	transGossiper *gossip.Gossiper
	mtx           sync.Mutex
}

// Intialize -初始化共识
func (p *Pow) Intialize(difficulty int, nodeID string,
	ledger *blockchain.Ledger, beltline excitation.Beltline,
	blockGossiper *gossip.Gossiper, transGossiper *gossip.Gossiper) {
	p.difficulty = difficulty
	p.nodeID = nodeID
	Ledger = ledger
	p.beltline = beltline
	//	p.client.Prepare()
	p.blockGossiper = blockGossiper
	p.transGossiper = transGossiper
}

// Append - 将收到的区块连接到账本中
func (p *Pow) Append() {
	// for {
	// 将收到的区块连接到账本中
	for _, b := range p.blockGossiper.Items {
		if b == nil {
			break
		}

		block := blockchain.UnmarshalBlock(b)
		log.Println("> " + common.IntToString(block.Header.Index) + "---" + block.Header.Hash)
		// 收到第一个区块
		if Ledger.CurBlock.Header.Index == 1 {
			Ledger.AppendBlock(block)
			continue
		}
		// 收到其他区块
		p.mtx.Lock()
		// preblock, err := Ledger.BDB.Get([]byte(block.Header.PrevHash))
		// if err != nil {
		// 	log.Println(err)
		// }
		// if preblock != nil {
		// 	preb := blockchain.UnmarshalBlock(preblock)
		// 	preblock, err := Ledger.BDB.Get([]byte(block.Header.PrevHash))
		// }
		if p.IsBlockValid(Ledger.CurBlock, block) {
			Ledger.AppendBlock(block)
		}
		p.mtx.Unlock()
	}

	// time.Sleep(5 * time.Second)
	// }
}

// Work - 产生区块
func (p *Pow) Work() {
	for {
		// pow生成区块
		var block *blockchain.Block
		if block = p.generateBlock(); block == nil {
			continue
		}

		// 产生激励
		if prize, err := p.beltline.Yield(p.nodeID); err == nil {
			p.wrap(prize, block)
		}

		// 记录区块
		Ledger.AppendBlock(*block)
		// 广播区块,同时也会广播给自己
		if data, err := blockchain.MarshalBlock(*block); err == nil {
			p.blockGossiper.Add(block.Header.Hash, data, "b")
		}
	}
}

// wrap - 区块封包
func (p *Pow) wrap(prize string, block *blockchain.Block) {
	var bucketTree tree.BucketTree
	bucketTree.Build()

	tran := blockchain.Transaction{Data: []byte(prize), Committer: p.nodeID, Time: time.Now()}
	d, _ := blockchain.MarshalTransaction(tran)
	// 写入激励
	prizeEnt := &tree.Entry{Owner: nil,
		TimeStamp: tran.Time,
		Data:      d}
	prizeEnt.Hash = crytography.Hash(common.BytesCombine(prizeEnt.Data, common.Int64ToBytes(prizeEnt.TimeStamp.UnixNano())))
	bucketTree.PushEntry(prizeEnt)
	p.transGossiper.Add(prizeEnt.Hash, d, "t")

	// 写入交易
	for _, x := range p.transGossiper.Items {
		ent := &tree.Entry{Owner: nil,
			TimeStamp: time.Now(),
			Data:      x}
		ent.Hash = crytography.Hash(common.BytesCombine(ent.Data, common.Int64ToBytes(ent.TimeStamp.UnixNano())))
		bucketTree.PushEntry(ent)
	}

	block.Header.MerkleRoot = bucketTree.MerkleRoot.Hash
	block.Body.Trans = p.transGossiper.Items
	p.transGossiper.Items = make(map[string][]byte)
}

// IsBlockValid - make sure block is valid by checking index, and comparing the hash of the previous block
func (p *Pow) IsBlockValid(oldBlock, newBlock blockchain.Block) bool {
	if (oldBlock.Header.Index + 1) != newBlock.Header.Index {
		log.Println("> " + common.IntToString(oldBlock.Header.Index) + "---" + common.IntToString(newBlock.Header.Index))
		return false
	}

	if oldBlock.Header.Hash != newBlock.Header.PrevHash {
		log.Println("> " + oldBlock.Header.Hash + "---" + newBlock.Header.PrevHash)
		return false
	}

	if str := p.CalculateHash(&newBlock); str != newBlock.Header.Hash {
		log.Println("> " + str + "---" + newBlock.Header.Hash)
		return false
	}

	return true
}

// CalculateHash - 计算hash
func (p *Pow) CalculateHash(block *blockchain.Block) string {
	record := strconv.Itoa(block.Header.Index) +
		block.Header.PrevHash +
		block.Header.Timestamp +
		string(block.Header.Version) +
		string(block.Header.Nonce) +
		block.Header.Validator

	data := []byte(record)

	return crytography.Hash(data)
}

// create a new block using previous block's hash
func (p *Pow) generateBlock() *blockchain.Block {
	if Ledger.CurBlock.Header.Hash == "" {
		return nil
	}

	var newBlock blockchain.Block
	newBlock.Header = blockchain.BlockHeader{}
	newBlock.Header.Version = 0x01
	newBlock.Header.Nonce = 1
	newBlock.Header.Validator = ""

	// 计算
	for i := 0; ; i++ {
		// hex := fmt.Sprintf("%x", i)
		b := Ledger.CurBlock
		newBlock.Header.Index = b.Header.Index + 1
		newBlock.Header.Timestamp = time.Now().String()
		newBlock.Header.PrevHash = b.Header.Hash
		newBlock.Header.Nonce = i
		if !p.isHashValid(p.CalculateHash(&newBlock), p.difficulty) {
			log.Println(p.CalculateHash(&newBlock), " 不满足要求，继续..")
			time.Sleep(time.Second)
			continue
		} else {
			log.Println(p.CalculateHash(&newBlock), " 满足难度要求!")
			newBlock.Header.Hash = p.CalculateHash(&newBlock)
			break
		}

	}

	return &newBlock
}

func (p *Pow) isHashValid(hash string, difficulty int) bool {
	prefix := strings.Repeat("0", difficulty)
	return strings.HasPrefix(hash, prefix)
}
